#pragma once

#include <pthread.h>
#include <iostream>
#include <queue>
using namespace std;

/**
 * @brief 用互斥锁和条件变量来模拟阻塞队列，并构建出生产消费者模型
*/
template<class T>
class BlockQueue {
private:
    queue<T> _queue;
    int _max_capacity;
    pthread_mutex_t _mutex; // 因为生产者和消费者间存在互斥关系，所以它们需要共用同一把锁
    pthread_cond_t _consumer_condition_variable; // 消费者的条件变量，消费者在这里挂起
    pthread_cond_t _producer_condition_variable; // 生产者的条件变量，生产者在这里挂起

public:
    BlockQueue(const int& max_capacity = 10)
        : _max_capacity(max_capacity)
    {
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_consumer_condition_variable, nullptr);
        pthread_cond_init(&_producer_condition_variable, nullptr);
    }

    ~BlockQueue() {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_consumer_condition_variable);
        pthread_cond_destroy(&_producer_condition_variable);
    }

public:
    void push(const T& in) {
        pthread_mutex_lock(&_mutex); // 给队列上锁，满足生产者和生产者，生产者和消费者之间的互斥关系
        // 存放的前提是保证队列中有空间，这里是while的原因是，可能同时存在多个线程被条件变量挂起，
        // 如果用if，要是它们被同时唤醒的话，那么就会发生多个线程同时访问临界资源，如果线程数大于剩余资源数就会出现线程安全问题
        // 但是用while的话，就只会有小于等于临界资源的线程数才会进行访问，其他的依旧被挂起
        while (isFull()) {
            // 当队列已满时，将申请访问队列的线程全部挂起，等待呼叫
            // 线程在解除挂起状态时会申请mutex，如果申请到了就解除挂起，没申请到就继续挂起
            pthread_cond_wait(&_producer_condition_variable, &_mutex);
        }

        // 走到这里的话说明队列有剩余位置
        _queue.push(in);

        // 走到这里的话说明队列中决定至少有一个数据了
        // 叫醒在消费者条件变量中挂起的进程
        pthread_cond_signal(&_consumer_condition_variable);

        pthread_mutex_unlock(&_mutex);
    }

    void pop(T* out) {
        pthread_mutex_lock(&_mutex);

        while (isEmpty()) {
            pthread_cond_wait(&_consumer_condition_variable, &_mutex);
        }

        *out = _queue.front();
        _queue.pop();

        pthread_cond_signal(&_producer_condition_variable);

        pthread_mutex_unlock(&_mutex);
    }

private:
    bool isEmpty() const {
        return _queue.empty();
    }

    bool isFull() const {
        return _queue.size() == _max_capacity;
    }
};